use super::{file::FileBuilderProduct, RestoreError, RestoreResult, RestoreStatistics};
use crate::constants::RESTORE_DECODE_LOG;
use crate::container::ContainerStore;
use crate::rechunk::ReChunk;
use crate::recipe::ChunkPointer;
use std::{fs::File, time::Instant};
use std::io::Write;
use std::path::Path;
use std::marker::PhantomData;
use std::sync::Arc;
use parking_lot::Mutex;

use std::fmt::Debug;

use pipelines::{Receiver};

pub struct ReChunker<T:ChunkPointer+Debug> {
	path:String,
	statistics:Arc<Mutex<RestoreStatistics>>,
	data:PhantomData<T>,
}

impl<T:ChunkPointer+Debug> ReChunker<T> {
	pub fn from_path<P: AsRef<Path>+?Sized>(container_path:&P,statistics:Arc<Mutex<RestoreStatistics>>) -> Self {
		Self{
			path:container_path.as_ref().to_str().unwrap().to_string(),
			statistics:statistics,
			data:PhantomData,
		}
	}
	
	pub fn rechunk_async(&self,recv:Receiver<FileBuilderProduct<T>>) -> RestoreResult<()> {
		let mut cost_time:u128 = 0;
		let container_store = ContainerStore::from_path(&self.path,false)?;
		let mut cur_file:Option<File> = None;
		for p in recv {
			let now = Instant::now();
			match p {
				FileBuilderProduct::File(_,file) => {
					cur_file = Some(file);
				},
				FileBuilderProduct::Pointer(pointer) => {
					let rechunk = pointer.read(&container_store)?;
					match &rechunk as &ReChunk {
						ReChunk::Unique(rc) => 
							cur_file.as_mut().unwrap().write(&rc)
								.map_err(|e|RestoreError::FileWrite(e))?,
						ReChunk::Delta(rc) => {
							let base_pointer = rc.base_chunk_pointer();
							log::info!(target:RESTORE_DECODE_LOG,"chunk {:?} decode with base chunk {:?}\n",rc.fp(),base_pointer);
							let base_rechunk = base_pointer.read_exact(&container_store)?;
							let unique_base_rechunk = base_rechunk.unique_unwrap().ok_or(RestoreError::DoubleDelta)?;
							let origin_data = rc.restore(unique_base_rechunk).ok_or(RestoreError::Decode)?;
							cur_file.as_mut().unwrap().write(&origin_data)
								.map_err(|e|RestoreError::FileWrite(e))?
						}
					};
				}
			}
			cost_time += now.elapsed().as_micros();
		}
		self.statistics.lock().set_chunk_phase_time(cost_time);
		Ok(())
	}
}
